package lru

import (
	"container/list"
	"log"
	"time"
)

// Cache is a LRU cache. It is not safe for concurrent access.
type Cache struct {
	maxBytes int64      //允许使用的最大内存
	nowBytes int64      //当前已使用的内存
	ll       *list.List //
	cache    map[string]*list.Element
	// optional and executed when an entry is purged.
	OnEvicted  func(key string, value Value) //某条记录被移除时的回调函数
	defaultTTL time.Duration
}

type entry struct {
	key    string
	value  Value
	expire time.Time
}

// Value use Len to count how many bytes it takes
type Value interface {
	Len() int
}

// New is the Constructor of Cache
func New(maxBytes int64, onEvicted func(string, Value), defaultTTL time.Duration) *Cache {
	return &Cache{
		maxBytes:   maxBytes,
		ll:         list.New(),
		cache:      make(map[string]*list.Element),
		OnEvicted:  onEvicted,
		defaultTTL: defaultTTL,
	}
}

// Get look ups a key's value
// 从字典中找到对应的双向链表的节点，将该节点移动到队尾
func (c *Cache) Get(key string) (value Value, ok bool) {
	if ele, ok := c.cache[key]; ok {
		kv := ele.Value.(*entry)
		if kv.expire.Before(time.Now()) {
			c.RemoveElement(ele)
			log.Printf("The LRUcache key—%s has expired", key)
			return nil, false
		}
		c.ll.MoveToFront(ele)
		return kv.value, true
	}
	return
}

// RemoveOldest removes the oldest item
// 移除最近最少访问的节点(队首)
func (c *Cache) RemoveOldest() {
	for e := c.ll.Back(); e != nil; e = e.Prev() {
		kv := e.Value.(*entry)
		if kv.expire.Before(time.Now()) {
			c.RemoveElement(e)
			break
		}
	}
}

// Add adds a value to the cache.
func (c *Cache) Add(key string, value Value, ttl time.Duration) {
	if ele, ok := c.cache[key]; ok {
		c.ll.MoveToFront(ele)
		kv := ele.Value.(*entry)
		c.nowBytes += int64(value.Len()) - int64(kv.value.Len())
		kv.value = value
		//更新过期时间时，判断是否应该保留原本的过期时间
		if kv.expire.Before(time.Now().Add(ttl)) {
			kv.expire = time.Now().Add(ttl)
		}
	} else {
		ele := c.ll.PushFront(&entry{key: key, value: value, expire: time.Now().Add(ttl)})
		c.cache[key] = ele
		c.nowBytes += int64(len(key)) + int64(value.Len())
	}
	for c.maxBytes != 0 && c.maxBytes < c.nowBytes {
		c.RemoveOldest()
	}
}

// Len the number of cache entries
func (c *Cache) Len() int {
	return c.ll.Len()
}

func (c *Cache) RemoveElement(e *list.Element) {
	c.ll.Remove(e)
	kv := e.Value.(*entry)
	delete(c.cache, kv.key)
	c.nowBytes -= int64(len(kv.key)) + int64(kv.value.Len())
	if c.OnEvicted != nil {
		c.OnEvicted(kv.key, kv.value)
	}
}
